package service

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/log"

	pb "gitee.com/danqingzhao/go-dango/api/user/menu/v1"
	"gitee.com/danqingzhao/go-dango/apps/user/biz"
	"gitee.com/danqingzhao/go-dango/pkg/common/core"
	"gitee.com/danqingzhao/go-dango/pkg/proto/settings"
	"gitee.com/danqingzhao/go-dango/pkg/third/orm"
)

type MetaSchemas struct {
	Title string
	Icon  string
}

func (m *MetaSchemas) JsonData() string {
	jsonData, _ := json.Marshal(m)
	return string(jsonData)
}

type MenuService struct {
	pb.UnimplementedMenuServer
	log    *log.Helper
	ucMenu *biz.MenuUsecase
}

func NewMenuService(uc *biz.MenuUsecase, logHelpers *settings.LogHelpers) *MenuService {
	return &MenuService{ucMenu: uc, log: logHelpers.Server}
}

func (s *MenuService) CreateMenu(
	ctx context.Context,
	req *pb.CreateMenuRequest,
) (*pb.MenuReply, error) {
	metaJson, jsonErr := json.Marshal(pb.MetaSchemas{
		Icon:  req.Meta.Icon,
		Title: req.Meta.Title,
	})
	if jsonErr != nil {
		s.log.WithContext(ctx).Error(jsonErr.Error())
		return nil, errors.BadRequest(settings.ErrorReason_VALIDATOR.String(), jsonErr.Error()).WithCause(jsonErr)
	}
	im := biz.MenuModel{
		Path:         req.Path,
		Component:    req.Component,
		Name:         req.Name,
		AppName:      req.AppName,
		ArrangeOrder: req.ArrangeOrder,
		Meta:         string(metaJson),
		IsActive:     req.IsActive,
		Descr:        req.Descr,
	}
	om, err := s.ucMenu.CreateMenu(ctx, req.ParentId, req.PermissionIds, im)
	if err != nil {
		return nil, err
	}
	data, err := MenuSchemasOut(ctx, s.log, *om)
	if err != nil {
		return nil, err
	}
	return &pb.MenuReply{Code: http.StatusCreated, Message: "", Metadata: data}, nil
}

func (s *MenuService) UpdateMenu(
	ctx context.Context,
	req *pb.UpdateMenuRequest,
) (*pb.MenuReply, error) {
	meta := &MetaSchemas{
		Icon:  req.Meta.Icon,
		Title: req.Meta.Title,
	}
	im := biz.MenuModel{
		Id:           req.Pk,
		Path:         req.Path,
		Component:    req.Component,
		Name:         req.Name,
		AppName:      req.AppName,
		ArrangeOrder: req.ArrangeOrder,
		Meta:         meta.JsonData(),
		IsActive:     req.IsActive,
		Descr:        req.Descr,
	}
	if req.PermissionIds == nil {
		req.PermissionIds = []uint32{}
	}
	om, err := s.ucMenu.UpdateMenuById(ctx, req.Pk, req.ParentId, req.PermissionIds, im)
	if err != nil {
		return nil, err
	}
	data, err := MenuSchemasOut(ctx, s.log, *om)
	if err != nil {
		return nil, err
	}
	return &pb.MenuReply{Code: http.StatusOK, Message: "", Metadata: data}, nil
}

func (s *MenuService) DeleteMenu(
	ctx context.Context,
	req *pb.DeleteMenuRequest,
) (*pb.MenuReply, error) {
	if err := s.ucMenu.DeleteMenuById(ctx, req.Pk); err != nil {
		return nil, err
	}
	return &pb.MenuReply{Code: http.StatusOK, Message: "", Metadata: nil}, nil
}

func (s *MenuService) GetMenu(
	ctx context.Context,
	req *pb.GetMenuRequest,
) (*pb.MenuReply, error) {
	om, err := s.ucMenu.FindMenuByID(ctx, []string{"Parent", "Permission"}, req.Pk)
	if err != nil {
		return nil, err
	}
	data, err := MenuSchemasOut(ctx, s.log, *om)
	if err != nil {
		return nil, err
	}
	return &pb.MenuReply{Code: http.StatusOK, Message: "", Metadata: data}, nil
}

func (s *MenuService) ListMenu(
	ctx context.Context,
	req *pb.ListMenuRequest,
) (*pb.PagMenuReply, error) {
	page, size, query := s.filterMenu(req)
	count, ms, err := s.ucMenu.ListMenu(ctx, nil, page, size, query)
	if err != nil {
		return nil, err
	}
	mso, err := ListMenuModelToSchemasOutBase(ctx, s.log, ms)
	if err != nil {
		return nil, err
	}
	rsp := pb.PagMenuOutBase{
		Items: mso,
		Page:  int64(page),
		Pages: core.CountPages(count, int64(size)),
		Size:  int64(size),
		Total: count,
	}
	return &pb.PagMenuReply{Code: http.StatusOK, Message: "", Metadata: &rsp}, nil
}

func (s *MenuService) filterMenu(
	req *pb.ListMenuRequest,
) (int, int, map[string]any) {
	var (
		page  int            = core.DefaultPage
		size  int            = core.DefaultSize
		query map[string]any = make(map[string]any)
	)
	if req != nil {
		if req.Page > 0 {
			page = int(req.Page)
		}
		if req.Size > 0 {
			size = int(req.Size)
		}
		if req.Pk != 0 {
			query["id = ?"] = req.Pk
		} else if req.Pks != "" {
			pks := core.StringToListUint32(req.Pks)
			if len(pks) > 1 {
				query["id in ?"] = pks
			}
		}
		if req.ParentId != 0 {
			query["parent_id = ?"] = req.ParentId
		}
		if req.ParentNull != nil {
			query["parent_id is ?"] = "NULL"
		}
		if req.Path != "" {
			query["path like ?"] = req.Path
		}
		if req.Component != "" {
			query["component like ?"] = req.Component
		}
		if req.Name != "" {
			query["name like ?"] = req.Name
		}
		if req.AppName != "" {
			query["model = ?"] = req.AppName
		}
		if req.IsActive != nil {
			query["is_active = ?"] = req.IsActive
		}
	}
	return page, size, query
}

func metaOut(
	logHelper *log.Helper,
	m biz.MenuModel,
) (*pb.MetaSchemas, *errors.Error) {
	if m.Meta == "" {
		err := errors.InternalServer(
			orm.ErrorReason_GORM_MODEL_ACCESSIBLE_FIELDS_REQUIERD.String(),
			"model accessible fields required",
		).WithMetadata(map[string]string{"field": "Meta"})
		logHelper.Error(err.Message)
		return nil, err
	}
	var metaMap map[string]string
	err := json.Unmarshal([]byte(m.Meta), &metaMap)
	if err != nil {
		rErr := errors.InternalServer(
			settings.ErrorReason_VALIDATOR.String(),
			fmt.Sprintf("menu meta json decode error, json data: %s", m.Meta),
		).WithCause(err)
		return nil, rErr
	}
	return &pb.MetaSchemas{Icon: metaMap["icon"], Title: metaMap["title"]}, nil
}

func MenuSchemasOutBase(
	ctx context.Context,
	logHelper *log.Helper,
	m biz.MenuModel,
) (*pb.MenuOutBase, *errors.Error) {
	metaSchemas, err := metaOut(logHelper, m)
	if err != nil {
		return nil, err
	}
	var parentId uint32 = 0
	if m.ParentId != nil {
		parentId = *m.ParentId
	}
	return &pb.MenuOutBase{
		Id:           m.Id,
		CreatedTime:  m.CreatedAt.String(),
		UpdatedTime:  m.UpdatedAt.String(),
		Path:         m.Path,
		Component:    m.Component,
		Name:         m.Name,
		AppName:      m.AppName,
		Meta:         metaSchemas,
		ArrangeOrder: m.ArrangeOrder,
		IsActive:     m.IsActive,
		Descr:        m.Descr,
		ParentId:     parentId,
	}, nil
}

func MenuSchemasOut(
	ctx context.Context,
	logHelper *log.Helper,
	m biz.MenuModel,
) (*pb.MenuOut, *errors.Error) {
	metaSchemas, err := metaOut(logHelper, m)
	if err != nil {
		return nil, err
	}
	pmso := ListPermissionModelToSchemasOutBase(ctx, logHelper, m.Permissions)
	var parent *pb.MenuOutBase
	if m.Parent != nil {
		p, err := MenuSchemasOutBase(ctx, logHelper, *m.Parent)
		if err != nil {
			return nil, err
		}
		parent = p
	}
	return &pb.MenuOut{
		Id:           m.Id,
		CreatedTime:  m.CreatedAt.String(),
		UpdatedTime:  m.UpdatedAt.String(),
		Path:         m.Path,
		Component:    m.Component,
		Name:         m.Name,
		AppName:      m.AppName,
		Meta:         metaSchemas,
		ArrangeOrder: m.ArrangeOrder,
		IsActive:     m.IsActive,
		Descr:        m.Descr,
		Parent:       parent,
		Permissions:  pmso,
	}, nil
}

func ListMenuModelToSchemasOutBase(
	ctx context.Context,
	logHelper *log.Helper,
	ms []biz.MenuModel,
) ([]*pb.MenuOutBase, *errors.Error) {
	mso := make([]*pb.MenuOutBase, 0, len(ms))
	if len(ms) > 0 {
		for _, m := range ms {
			mo, err := MenuSchemasOutBase(ctx, logHelper, m)
			if err != nil {
				return nil, err
			}
			mso = append(mso, mo)
		}
	}
	return mso, nil
}
